package pATT.bNEdit.gui;


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

import pATT.bNEdit.base.Nodo;
import pATT.bNEdit.jgraph.org.jgraph.JGraph;
import pATT.bNEdit.jgraph.org.jgraph.event.GraphModelEvent;
import pATT.bNEdit.jgraph.org.jgraph.event.GraphModelListener;
import pATT.bNEdit.jgraph.org.jgraph.event.GraphSelectionEvent;
import pATT.bNEdit.jgraph.org.jgraph.event.GraphSelectionListener;
import pATT.bNEdit.jgraph.org.jgraph.graph.AttributeMap;
import pATT.bNEdit.jgraph.org.jgraph.graph.ConnectionSet;
import pATT.bNEdit.jgraph.org.jgraph.graph.DefaultEdge;
import pATT.bNEdit.jgraph.org.jgraph.graph.DefaultGraphCell;
import pATT.bNEdit.jgraph.org.jgraph.graph.DefaultGraphModel;
import pATT.bNEdit.jgraph.org.jgraph.graph.DefaultPort;
import pATT.bNEdit.jgraph.org.jgraph.graph.GraphConstants;
import pATT.bNEdit.jgraph.org.jgraph.graph.Port;
import pATT.bNEdit.obj17.View;



@SuppressWarnings("unchecked")
public class NetView extends JPanel implements
   GraphModelListener, GraphSelectionListener{

 /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
private JGraph graph;
 private Hashtable ports = new Hashtable();
 private Hashtable cells = new Hashtable();
 private Vector monitorCells;
 private BNEditInternalFrame internalFrame;


 JPopupMenu PopupMenuCell ;

 JMenuItem MenuItemAddExpTable = new JMenuItem();
 JMenuItem MenuItemDeleteExpTable=new JMenuItem();
 JMenuItem MenuItemRun=new JMenuItem();
 JMenuItem MenuItemNodeProperties = new JMenuItem();
 JMenuItem MenuItemEdit=new JMenuItem();
 JMenuItem MenuItemShowMonitorWindow= new JMenuItem();
 JMenuItem MenuItemShowDefaultWindow=new JMenuItem();
 JMenuItem MenuItemAdaptation= new JMenuItem();

 public NetView (){

   graph = new JGraph2(new DefaultGraphModel());
   graph.setMarqueeHandler(new BNMarqueeHandler(graph, this));
//   graph.setSelectNewCells(true);
   graph.getModel().addGraphModelListener(this);
   graph.addGraphSelectionListener(this);
   this.setLayout(new BorderLayout());
   this.add(graph,BorderLayout.CENTER);
   monitorCells=new Vector();

 }

 public void setBNEditInternalFrame(BNEditInternalFrame frame){
   internalFrame=frame;

 }

   /**
   * Gets the default port for the given vertex
   * @param vertex Object - the vertex
   * @return Port - the vertex's default port
   */
  public Port getPort(Object vertex) {
    return (Port) ports.get(vertex);
  }



  /**
   * Gets the cell for the given vertex
   * @param vertex Object - the vertex
   * @return Cell - the vertex's cell
   */
  public Cell getCell(Object vertex){
    return (Cell) cells.get(vertex);
  }


  /**
   * Inserts an edge between the source and the target cells
   * @param userObject Object - the edge object
   * @param source Port - the source port
   * @param target Port - the target port
   */
public void connect(String source, String target) {
    Port sourcePort = getPort(source);
    Port targetPort = getPort(target);
    // Create Edge
    Link link = new Link();
    link.source = internalFrame.getBayesianNetwork().getNodo(source);
    link.target = internalFrame.getBayesianNetwork().getNodo(target);
    DefaultEdge edge = new DefaultEdge(link);
    // Create ConnectionSet for Insertion
    ConnectionSet cs = new ConnectionSet(edge, sourcePort, targetPort);
    // Add Edge and Connections to the Model
    Object[] insert = new Object[] {edge};
    // Attributes
    Map attributeMap = new Hashtable();
    Map map = new Hashtable();
    attributeMap.put(edge, map);
    GraphConstants.setLineEnd(map, GraphConstants.ARROW_TECHNICAL);
    GraphConstants.setEndFill(map, true);
    GraphConstants.setEditable(map, false);
    GraphConstants.setDisconnectable(map, false);
    GraphConstants.setLabelAlongEdge(map, true);
    graph.getModel().insert(insert, attributeMap, cs, null, null);
    graph.getModel().toBack(new Object[]{edge});
  }

  /**
   * Rename a variable in the frame's bayesian network
   * @param oldName String - the old name of the variable
   * @param newName String - the new name
   */
  public void renameVar(String oldName, String newName) {
     Cell actualcell=(Cell) getCell(oldName);
     DefaultGraphCell cell = actualcell.getCell();
     Map attributes = cell.getAttributes();
     GraphConstants.setValue(attributes, newName);
     Map edit = new Hashtable();
     edit.put(cell, attributes);
     graph.getModel().edit(edit, null, null, null);
     cells.remove(oldName);
     cells.put(newName, actualcell);
     ports.put(newName, ports.get(oldName));
     ports.remove(oldName);
  }

  /**
   * Deletes a variable from the bayesian network;
   * @param varName String - the name of the variable to delete
   */
  public void deleteVar(String varName) {
    if (internalFrame.getBayesianNetwork().delNodo(varName)) {
        delNode(varName);
    }
  }

   protected void delNode(String varName){
     DefaultGraphCell cell = ((Cell)cells.get(varName)).getCell();

     int childCount = graph.getModel().getChildCount(cell);
      Object[] objects = new Object[childCount+1];
      for (int i=0; i<childCount; i++) {
        objects[i] = graph.getModel().getChild(cell, i);
        if (objects[i] instanceof DefaultPort) {
          DefaultPort port = (DefaultPort) objects[i];
          for (Iterator iter = port.getEdges().iterator()
               ; iter.hasNext(); ) {
            Object item = (Object)iter.next();
            graph.getModel().remove(new Object[] {item});
          }
        }
      }
      objects[childCount] = cell;
      graph.getModel().remove(objects);
   }

  /**
   * Deletes the specified link from the bayesian network
   * @param source String - the name of the source variable
   * @param target String - the name of the target variable
   */
  public void deleteLink(String source, String target) {
    if (internalFrame.getBayesianNetwork().delArco(source, target)) {
      DefaultGraphCell sourceCell = ((Cell)cells.get(source)).getCell();
      Set sourceEdges = new HashSet();
      int childCount = graph.getModel().getChildCount(sourceCell);
      for (int i=0; i<childCount; i++) {
        Object child = graph.getModel().getChild(sourceCell,i);
        if (child instanceof DefaultPort) {
          for (Iterator iter = ((DefaultPort)child).getEdges().iterator(); iter.hasNext(); ) {
            DefaultEdge edge = (DefaultEdge)iter.next();
            sourceEdges.add(edge);
          }
        }
      }
      DefaultGraphCell targetCell = ((Cell)cells.get(target)).getCell();
      childCount = graph.getModel().getChildCount(targetCell);
      for (int i=0; i<childCount; i++) {
        Object child = graph.getModel().getChild(targetCell, i);
        if (child instanceof DefaultPort) {
          for (Iterator iter = ( (DefaultPort) child).getEdges().iterator(); iter.hasNext(); ) {
            DefaultEdge edge = (DefaultEdge) iter.next();
            if (sourceEdges.contains(edge)) {
              graph.getModel().remove(new Object[] {edge});
            }
          }
        }
      }
    }
  }

  /**
   * valueChanged - method implemented for GraphSelectionListener interface
   * @param graphSelectionEvent GraphSelectionEvent
   */
  public void valueChanged(GraphSelectionEvent graphSelectionEvent) {
    DefaultGraphCell cell = (DefaultGraphCell) graphSelectionEvent.getCell();
    if (cell!=null && graphSelectionEvent.isAddedCell() &&
        cell instanceof DefaultEdge) {
      Link link = (Link) cell.getUserObject();
      internalFrame.newEdgeSelection(link);
    }
    else if (cell!=null && graphSelectionEvent.isAddedCell() &&
        cell instanceof DefaultGraphCell) {
      String varName = (String) cell.getUserObject();
      internalFrame.newNodeSelection(varName);
    }
    else
      internalFrame.nullSelection();
  }

  /**
   * graphChanged
   * JGraph notifies when the graph is changed. Variables are updated with
   * their new positions.
   * @param graphModelEvent GraphModelEvent
   */
  public void graphChanged(GraphModelEvent graphModelEvent) {
    internalFrame.netHasChanged();
    internalFrame.mainFrame().someChange();
    GraphModelEvent.GraphModelChange change = graphModelEvent.getChange();
    Object[] changed = change.getChanged();
    for (int i = 0; i < changed.length; i++) {
      DefaultGraphCell cell = (DefaultGraphCell) changed[i];
      if (cell!=null) {
        Object userObject = cell.getUserObject();
        if (userObject != null && userObject instanceof String) {
          Rectangle2D cellBounds = GraphConstants.getBounds(cell.getAttributes());
          Point newPosition = new Point( (int) cellBounds.getX(),
                                         (int) cellBounds.getY());
          Nodo node = internalFrame.getBayesianNetwork().getNodo((String) userObject);
          if (node!=null)
            node.setPosicion(newPosition.x, newPosition.y);
        }
      }
    }
  }


  /**
   * Adds a variable to the Bayesian Network at the specified position
   * @param variableName String - name of the variable
   * @param position Point - position of the variable
   */
  public boolean addVariable(String variableName, Point position) {
    if (internalFrame.getBayesianNetwork().addNodo(variableName, position.x, position.y)) {
      this.insertVertex(new Default(variableName),position);
      internalFrame.mainFrame().setStatus("Variable " + variableName + " added.");
      return true;
    }
    return false;

  }

  /**
   * Inserts a vertex in the graphModel
   * @param userObject Object - the object contained in the cell (should
   * implement toString)
   * @param bounds Rectangle - the bounds of the inserted vertex
   */
  public Port insertVertex(Cell actualcell, Point position) {
     // Map that Contains Attribute Maps
     Map attributeMap = new Hashtable();
     // Create Attribute Map for Cell
     Map map = new Hashtable();
     Dimension size = actualcell.getDimension();
     GraphConstants.setBounds(map, new Rectangle(position, size));
     GraphConstants.setOpaque(map, true);
     GraphConstants.setBackground(map, Color.yellow);
     GraphConstants.setBorderColor(map, Color.black);
     GraphConstants.setEditable(map, false);
     GraphConstants.setSizeable(map, false);
     // Associate Attribute Map with Cell
     attributeMap.put(actualcell.getCell(), map);
     // Create Default Doubleing Port
     DefaultPort port = new DefaultPort();
     actualcell.add(port);
     // Add Cell (and Children) to the Model
     Object[] insert = new Object[] {actualcell.getCell()};
     graph.getModel().insert(insert, attributeMap, null, null, null);
     ports.put(actualcell.getUserObject(), port);
     cells.put(actualcell.getUserObject(), actualcell);
     return port;
  }


  public void insertVertex (String varName, Point position){
    this.insertVertex(new Default(varName), position);
  }

  /**
   * Adds a variable with a default name at the specified point
   * @param position Point
   */
  public void addVariable(Point position) {
    int n = 0;
    while (!this.addVariable("C" + n, position))
      n++;
  }

  /**
   * Adds a link between source and target variables
   * @param source String
   * @param target String
   */
  public void addLink(String source, String target) {
   if (!existeArco(source, target)){
     if (internalFrame.getBayesianNetwork().addArco(source, target)) {
       this.connect(source, target);
       internalFrame.mainFrame().setStatus("Link betweeen " + source + " and " +
                                         target + " added.");
     }
   }
  }

  /**
   * Check there isn't a connection between Origen and Destino
   * @param origen String - the vertex
   * @param destino String - the vertex
   * @return boolean
   */
  protected boolean existeArco (String origen, String destino){
    DefaultGraphCell sourceCell = ((Cell)cells.get(origen)).getCell();
//    Set sourceEdges = new HashSet();
      int childCount = graph.getModel().getChildCount(sourceCell);
      for (int i=0; i<childCount; i++) {
        Object child = graph.getModel().getChild(sourceCell,i);
        if (child instanceof DefaultPort) {
          for (Iterator iter = ((DefaultPort)child).edges(); iter.hasNext(); ) {
            DefaultEdge edge = (DefaultEdge)iter.next();
            if (edge.getSource()==getPort(origen) && edge.getTarget()==getPort(destino))
              return true;
          }
        }
      }
   return false;
  }

  /**
   * Lists all connections the given vertex has got.
   * @param varName - the vertex
   * @return Vector with constains all connections
   */
  public Vector getArcos(String varName){

    Vector res = new Vector();
    DefaultGraphCell sourceCell = ((Cell) cells.get(varName)).getCell();
//    Set sourceEdges = new HashSet();
    int childCount = graph.getModel().getChildCount(sourceCell);
    for (int i = 0; i < childCount; i++) {
      Object child = graph.getModel().getChild(sourceCell, i);
      if (child instanceof DefaultPort) {
        for (Iterator iter = ( (DefaultPort) child).edges(); iter.hasNext(); ) {
          DefaultEdge edge = (DefaultEdge) iter.next();
          res.add(edge.getUserObject());
        }
      }
    }
    return res;
  }


  public int getMode() {
   return internalFrame.getMode();
  }

  public void resetMode() {
   internalFrame.resetMode();
  }

  public int getConstNode(){
   return BNEditInternalFrame.MODE_NODE;
  }

  public int getConstArc(){
   return BNEditInternalFrame.MODE_ARC;
  }

  public int getConstSelect(){
   return BNEditInternalFrame.MODE_SELECT;
  }

  public void setEnabled(boolean state){
    graph.setEnabled(state);
  }

  /**
   * this method is called every time the user
   * changes from run type to editor type. In this case, all monitor views
   * have to change to default views.
   */
  public void changeToDefaultView(){
    while (monitorCells.size()!=0){
      changeToDefault((String) monitorCells.get(0));
    }
  }

  /**
   * Changes the given vertex to monitor view
   * @param Name - the vertex's name
   */
  public void changeToMonitor(String Name){
    monitorCells.add(Name);

    View v=internalFrame.runPanel.getView(Name);
    Cell newcell=new Monitor(Name, v);

    changeView(Name, newcell);
  }

  /**
   * Changes the given vertex to default view
   * @param Name - the vertex's name
   */
  public void changeToDefault(String Name) {
    if (monitorCells.remove(Name)){
      Cell newcell=new Default(Name);

      changeView(Name, newcell);
    }
  }

  /**
   * Change the vertex's view
   * @param Name - the vertex's name
   * @param newcell - the new cell
   */
  protected void changeView(String Name, Cell newcell){
    DefaultGraphCell cell = ( (Cell) getCell(Name)).getCell();
    AttributeMap attributes = cell.getAttributes();
    int x = (int) GraphConstants.getBounds(attributes).getX();
    int y = (int) GraphConstants.getBounds(attributes).getY();
    Point position = new Point(x, y);
    Vector edges = getArcos(Name);
//    Map attributeMap = new Hashtable();
//    Map map = new Hashtable();
    this.delNode(Name);
    this.insertVertex(newcell, position);
    for (Enumeration e = edges.elements(); e.hasMoreElements(); ) {
       Link link = (Link) e.nextElement();
       String source = internalFrame.getBayesianNetwork().NombreExt( (Nodo)
           link.source);
       String target = internalFrame.getBayesianNetwork().NombreExt( (Nodo)
           link.target);
       this.connect(source, target);
    }
  }

  public Point getLocationOnSreen(){
    return  internalFrame.getLocationOnScreen();
  }

  public boolean isEditorType(){
     return internalFrame.isEditorType();
  }

  /**
   * Creates the Popup menu for the given node,
   * when the network is on editor type
   * @param varName - the node's name
   */
  public void createEditPopupMenu(String varName){
    PopupMenuCell=new JPopupMenu();

    MenuItemAddExpTable.setText("Add Experience Table");
    MenuItemAddExpTable.addActionListener(new NetView_MenuItemAddExpTable_actionAdapter(this));

    MenuItemDeleteExpTable.setText("Delete Experience Table");
    MenuItemDeleteExpTable.addActionListener(new NetView_MenuItemDeleteExpTable_actionAdapter(this));

    MenuItemRun.setText("Run");
    MenuItemRun.addActionListener(new NetView_MenuItemRun_actionAdapter(this));
    MenuItemRun.setIcon(ResourceManager.getIcon("run.gif"));

    MenuItemNodeProperties.setText("Node Properties");
    MenuItemNodeProperties.addActionListener(new NetView_MenuItemNodeProperties_actionAdapter(this));
    MenuItemNodeProperties.setIcon(ResourceManager.getIcon("nodeproperties.gif"));

    PopupMenuCell.add(MenuItemRun);
    PopupMenuCell.addSeparator();
    PopupMenuCell.add(MenuItemAddExpTable);
    PopupMenuCell.add(MenuItemDeleteExpTable);
    PopupMenuCell.addSeparator();
    PopupMenuCell.add(MenuItemNodeProperties);

    // sets options related to Experience table
    if (!internalFrame.getBayesianNetwork().getNodo(varName).hasExperienceTable()){
        MenuItemDeleteExpTable.setEnabled(false);
        MenuItemAddExpTable.setEnabled(true);
    }
    else{
        MenuItemAddExpTable.setEnabled(false);
        MenuItemDeleteExpTable.setEnabled(true);
        }
    //sets options related to the panel where tables are shown
    if (internalFrame.tablesPanel.isVisible()){
        MenuItemRun.setEnabled(false);
        MenuItemNodeProperties.setEnabled(false);
    }
    else{
        MenuItemRun.setEnabled(true);
        MenuItemNodeProperties.setEnabled(true);
    }
  }

  /**
   * Creates a popup menu for the given node,
   * when the network is on run type
   * @param varName - the node's name
   */
  public void createRunPopupMenu(String varName){
      PopupMenuCell=new JPopupMenu();

      MenuItemEdit.setText("Edit");
      MenuItemEdit.addActionListener(new NetView_MenuItemEdit_actionAdapter(this));
      MenuItemEdit.setIcon(ResourceManager.getIcon("edit.gif"));

      MenuItemShowMonitorWindow.setText("Show Monitor Window");
      MenuItemShowMonitorWindow.addActionListener(new NetView_MenuItemShowMonitorWindow_actionAdapter(this));

      MenuItemShowDefaultWindow.setText("Close Monitor Window");
      MenuItemShowDefaultWindow.addActionListener(new NetView_MenuItemShowDefaultWindow_actionAdapter(this));

      MenuItemAdaptation.setText("Adaptation");
      MenuItemAdaptation.addActionListener(new NetView_MenuItemAdaptation_actionAdapter(this));
      MenuItemAdaptation.setIcon(ResourceManager.getIcon("adaptation.gif"));

      PopupMenuCell.add(MenuItemEdit);
      PopupMenuCell.addSeparator();

      //Allows to change the view
      if (((Cell) cells.get(varName)) instanceof Default)
        PopupMenuCell.add(MenuItemShowMonitorWindow);
        else
          PopupMenuCell.add(MenuItemShowDefaultWindow);
     //Allows to select the Adaptation button when the given node has an
     //experience table
     if (internalFrame.getBayesianNetwork().getNodo(varName).hasExperienceTable())
        PopupMenuCell.add(MenuItemAdaptation);
   }

   public void processMouseEvent (MouseEvent e){
     PopupMenuCell.show(this, e.getX(), e.getY());
   }

  public void addExpTable() {
    internalFrame.addExpTable();
  }

  public void deleteExpTable() {
    internalFrame.delExpTable();
  }

  public void run() {
    internalFrame.buttonRun.setSelected(true);
    internalFrame.itemRun_Selected();
  }

  public void NodeProperties() {
    internalFrame.toggleNodeProperties.setSelected(true);
    internalFrame.itemNodeProperties_Selected();
  }

  public void edit() {
    internalFrame.buttonRun.setSelected(false);
    internalFrame.itemEdit_Selected();
  }

  public void showMonitorWindow() {
    internalFrame.setMonitorView();
  }

  public void showDefaultWindow() {
    this.changeToDefault((String) internalFrame.selected());
  }

  public void adaptation() {
     internalFrame.adaptation();
  }

}

class NetView_MenuItemAddExpTable_actionAdapter implements java.awt.event.ActionListener {
  NetView adaptee;

  NetView_MenuItemAddExpTable_actionAdapter(NetView adaptee) {
    this.adaptee = adaptee;
  }
  public void actionPerformed(ActionEvent e) {
    adaptee.addExpTable();
  }
}

class NetView_MenuItemDeleteExpTable_actionAdapter implements java.awt.event.ActionListener {
  NetView adaptee;

  NetView_MenuItemDeleteExpTable_actionAdapter(NetView adaptee) {
    this.adaptee = adaptee;
  }
  public void actionPerformed(ActionEvent e) {
    adaptee.deleteExpTable();
  }
}

class NetView_MenuItemRun_actionAdapter implements java.awt.event.ActionListener {
  NetView adaptee;

  NetView_MenuItemRun_actionAdapter(NetView adaptee) {
    this.adaptee = adaptee;
  }
  public void actionPerformed(ActionEvent e) {
    adaptee.run();
  }
}

class NetView_MenuItemNodeProperties_actionAdapter implements java.awt.event.ActionListener {
  NetView adaptee;

  NetView_MenuItemNodeProperties_actionAdapter(NetView adaptee) {
    this.adaptee = adaptee;
  }
  public void actionPerformed(ActionEvent e) {
    adaptee.NodeProperties();

  }
}

class NetView_MenuItemEdit_actionAdapter implements java.awt.event.ActionListener {
  NetView adaptee;

  NetView_MenuItemEdit_actionAdapter(NetView adaptee) {
    this.adaptee = adaptee;
  }
  public void actionPerformed(ActionEvent e) {
    adaptee.edit();
  }
}

class NetView_MenuItemShowMonitorWindow_actionAdapter implements java.awt.event.ActionListener {
  NetView adaptee;

  NetView_MenuItemShowMonitorWindow_actionAdapter(NetView adaptee) {
    this.adaptee = adaptee;
  }
  public void actionPerformed(ActionEvent e) {
    adaptee.showMonitorWindow();
  }
}

class NetView_MenuItemShowDefaultWindow_actionAdapter implements java.awt.event.ActionListener {
  NetView adaptee;

  NetView_MenuItemShowDefaultWindow_actionAdapter(NetView adaptee) {
    this.adaptee = adaptee;
  }
  public void actionPerformed(ActionEvent e) {
    adaptee.showDefaultWindow();
  }
}

class NetView_MenuItemAdaptation_actionAdapter implements java.awt.event.ActionListener {
  NetView adaptee;

  NetView_MenuItemAdaptation_actionAdapter(NetView adaptee) {
    this.adaptee = adaptee;
  }
  public void actionPerformed(ActionEvent e) {
    adaptee.adaptation();
  }
}
